

#include "storage.h"
#include "cpu.h"

//#define DEBUG

struct stats_disk* io_cur_stat;
struct stats_disk* io_record_stat;
float *storage_util;
int iodev_nbr = 0;
int is_dsk_recording;
FILE *dsk_logger;
char dsk_filename[BUFFERSIZE];

extern int cpu_nbr;
extern int cpu_initialized;
long sdptot, sdctot;

int is_device(char *);

void storage_cleanup()
{
    if (io_cur_stat) free(io_cur_stat);
    if (io_record_stat) free(io_record_stat);
    if (storage_util) free(storage_util);
    if (dsk_logger != NULL) fclose(dsk_logger);
}

inline int disk_index_inside(int index)
{
    return 0 <= index && index < iodev_nbr;
}

unsigned long long get_interval_value()
{
    return interval;
}

void init_storage()
{
    if (!cpu_initialized)
            init_cpu();
    char dev_name[MAX_NAME_LEN], line[BUFFERSIZE];
    unsigned int major, minor;
    int i;
    unsigned long rd_ios, rd_merges, rd_ticks, wr_ios, wr_sectors, wr_ticks;
    unsigned long ios_pgr, tot_ticks, rq_ticks;
    unsigned long long rd_sectors, wr_merges;

    FILE *fp = fopen(DISKSTATS, "r");
    if (fp == NULL) return;
    while (fgets(line, BUFFERSIZE, fp) != NULL) {
        i = sscanf(line, "%u %u %s %lu %lu %llu %lu %lu %lu %llu %lu %lu %lu %lu",
                          &major, &minor, dev_name, &rd_ios, &rd_merges,
                          &rd_sectors, &rd_ticks, &wr_ios, &wr_merges,
                          &wr_sectors, &wr_ticks, &ios_pgr, &tot_ticks,
                          &rq_ticks);
        if (i == 14) { //
            if (!is_device(dev_name)) {
                //__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "init %s is not a device\n", dev_name);
                continue; // not a device
            }
            if (!rd_ios && !wr_ios) {
                //__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "init %s is not in use\n", dev_name);
                continue; // not in use
            }
            ++iodev_nbr;
        } else if (i == 7) {
            //__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "init %s is a partition\n", dev_name);
            continue; // Is a partition
        } else continue; // Unknown
    }
    fclose(fp);

    io_cur_stat
        = (struct stats_disk *)malloc((RESERVED_SLOTS + iodev_nbr) * sizeof(struct stats_disk));
    memset(io_cur_stat, 0, (RESERVED_SLOTS + iodev_nbr) * sizeof(struct stats_disk));

    io_record_stat
        = (struct stats_disk *)malloc((RESERVED_SLOTS + iodev_nbr) * sizeof(struct stats_disk));
    memset(io_record_stat, 0, (RESERVED_SLOTS + iodev_nbr) * sizeof(struct stats_disk));

    storage_util = (float*)malloc((iodev_nbr + RESERVED_SLOTS) * sizeof(float));
    memset(storage_util, 0, (iodev_nbr + RESERVED_SLOTS) * sizeof(float));

    // get storage names
    if (storage_dump())
        refresh_storage_util();
}

static int r_is_dsk_recording = 0;

void set_dsk_logging()
{
    if (loggable && is_dsk_recording && !r_is_dsk_recording) {
        char timesuffix[BUFFERSIZE];
        r_is_dsk_recording = 1;
        make_filename(timesuffix);
        snprintf(dsk_filename, BUFFERSIZE, "%s/storage_%s.log", LOGGER_PATH, timesuffix);
        dsk_logger = fopen(dsk_filename, "w");
        if (dsk_logger == NULL) {
            loggable = 0;
            // TODO notify UI that unable to create a file
            __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "dsk logging failed\n");
            return;
        }
        fprintf(dsk_logger, "seconds,devname,util%%,rd_ios,rd_merges,rd_sectors,rd_ticks,wr_ios,wr_merges,wr_sectors,wr_ticks,ios_pgr,tot_ticks,rq_ticks\n");
        __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "dsk logging started\n");
    } else if (r_is_dsk_recording && !is_dsk_recording) {
        r_is_dsk_recording = 0;
        if (loggable) {
            if (dsk_logger == NULL) return;
            fclose(dsk_logger);
            __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "dsk logging stopped\n");
            struct stat st;
            stat(dsk_filename, &st);
            if (st.st_size == 0) {
                remove(dsk_filename); // remove file if it's zero size
                __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "empty dsk logging removed\n");
            }
        }
    }
}

float get_storage_util(int index)
{
    if (disk_index_inside(index))
        return storage_util[index];
    return 0.0f;
}

long get_sdptot(int index)
{
    return (long)sdptot;
}

long get_sdctot(int index)
{
    return (long)sdctot;
}

int is_device(char *name)
{
    char syspath[BUFFERSIZE];
    char *slash;
    while ((slash = strchr(name, '/'))) {
        *slash = '!';
    }
    snprintf(syspath, sizeof(syspath), "%s/%s", SYSFS_BLOCK, name);
    return !(access(syspath, F_OK));
}

int storage_dump()
{
    FILE *fp;
    int i;
    char line[BUFFERSIZE];
    struct stats_disk *pt;
    char dev_name[MAX_NAME_LEN];
    unsigned int major, minor;
    unsigned long rd_ios, rd_merges, rd_ticks, wr_ios, wr_sectors, wr_ticks;
    unsigned long ios_pgr, tot_ticks, rq_ticks;
    unsigned long long rd_sectors, wr_merges;
    int disk_nbr = 0;
    fp = fopen(DISKSTATS, "r");
    if (fp == NULL) return;
    while (fgets(line, BUFFERSIZE, fp) != NULL) {
        i = sscanf(line, "%u %u %s %lu %lu %llu %lu %lu %lu %llu %lu %lu %lu %lu",
                          &major, &minor, dev_name, &rd_ios, &rd_merges,
                          &rd_sectors, &rd_ticks, &wr_ios, &wr_merges,
                          &wr_sectors, &wr_ticks, &ios_pgr, &tot_ticks,
                          &rq_ticks);
        if (i == 14) { //
            pt = io_cur_stat + disk_nbr;

            if (!is_device(dev_name)) continue; // not a device
            if (!rd_ios && !wr_ios) continue; // not in use
            // Save storage status
            pt->major = major;
            pt->minor = minor;
            strcpy(pt->name, dev_name);
            //__android_log_print(ANDROID_LOG_INFO, LOG_TAG,
            //                    "storage name %s\n", storage_name);
            pt->rd_ios = rd_ios;
            pt->rd_merges = rd_merges;
            pt->rd_sectors = rd_sectors;
            pt->rd_ticks = rd_ticks;
            pt->wr_ios = wr_ios;
            pt->wr_merges = wr_merges;
            pt->wr_sectors = wr_sectors;
            pt->wr_ticks = wr_ticks;
            pt->ios_pgr = ios_pgr;
            pt->tot_ticks = tot_ticks;
            pt->rq_ticks = rq_ticks;

            ++disk_nbr;

        } else if (i == 7) // Is a partition
            continue;
        else continue; // Unknown
    }
    fclose(fp);
    iodev_nbr = disk_nbr;
    return iodev_nbr;
}

void get_storage_name(char *buf, int index)
{
    buf[0] = 0;
    if (disk_index_inside(index)) {
        snprintf(buf, BUFFERSIZE, "%s", io_record_stat[index].name);
    }
    return;
}

void refresh_storage_util()
{
    int i;
    for (i=0; i<iodev_nbr; ++i) {
        sdptot = io_record_stat[i].tot_ticks;
        sdctot = io_cur_stat[i].tot_ticks;
        storage_util[i] = S_VALUE(sdptot, sdctot, interval) / 10.f;

        if (is_dsk_recording && loggable) {
                                //sec nm usage rio rmg rsec rtk wio wmg wsec wtk ipg ttk rtk
            fprintf(dsk_logger, "%llu,%s,%.3lf,%lu,%lu,%llu,%lu,%lu,%lu,%llu,%lu,%lu,%lu,%lu\n", 
                                system_time,
                                io_record_stat[i].name, 
                                storage_util[i],
                                io_cur_stat[i].rd_ios,
                                io_cur_stat[i].rd_merges,
                                io_cur_stat[i].rd_sectors,
                                io_cur_stat[i].rd_ticks,
                                io_cur_stat[i].wr_ios,
                                io_cur_stat[i].wr_merges,
                                io_cur_stat[i].wr_sectors,
                                io_cur_stat[i].wr_ticks,
                                io_cur_stat[i].ios_pgr,
                                io_cur_stat[i].tot_ticks,
                                io_cur_stat[i].rq_ticks);
            fflush(dsk_logger);
        }
    }
    swap_ptr((void*)&io_cur_stat, (void*)&io_record_stat);
}

int get_storage_total()
{
    return iodev_nbr;
}

float get_storage_total_util()
{
    float total = 0.0f;
    int i;
    for (i=0; i<iodev_nbr; ++i) {
        total += storage_util[i];
    }
    return total;
}
